/* 
 *  <copyright file="BaseActor.cs" company="Wesley Jowitt">
 *  
 *  SobrietyEngine: Copyright (c) 2011 Wesley Jowitt
 *  
 *  This software is distributed under a software license.
 *   * As of the creation time of this file, the license in use is the Microsoft Permissive License (Ms-PL) 
 *    * The license can be viewed at:
 *     * License.Txt in the root folder of this project
 *      * http://sobrietyengine.codeplex.com/license
 *       * http://www.microsoft.com/opensource/licenses.mspx
 *       
 *  </copyright>
 *   */

using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using SobrietyEngine.Components.Common;
using SobrietyEngine.Events;
using SobrietyEngine.Components;
using SobrietyEngine.Guids;
using SobrietyEngine.Logging;
using SobrietyEngine.Scene;

namespace SobrietyEngine.Actor
{

    public class BaseActor:IActor
    {
        String name;        

        CGuid guid;
        public CGuid Guid { get { return guid;  } }
        
        SobrietyEngine.Games.ISobrietyGameServices gameServices;
        public Game Game { get; private set; }
        
        Dictionary<Type, List<IComponent> > CompBySupportedIFaces;
        Dictionary<Type, List<IComponent> > CompByType;
        LinkedList<IComponent> requiresUpdate = new LinkedList<IComponent>();


        public IEventRouter EventRouter { get; set; }
        public Boolean UpdateActor { get; set; }
        protected Boolean Initialized = false;
        Boolean markedFoRemoval = false;
        public Boolean MarkedForRemoval
        {
            get { return markedFoRemoval; }
            set { markedFoRemoval = value; }
        }

        public IScene Scene { get; private set; }

        public BaseActor()
        {
            CompByType = new Dictionary<Type, List<IComponent>>();
            CompBySupportedIFaces = new Dictionary<Type, List<IComponent>>();
            guid = CGuid.Create(this);
            UpdateActor = true;
        }

        public BaseActor(Game _game, IScene scene)
            : this()
        {
            Scene = scene;
            Game = _game;
            gameServices = (SobrietyEngine.Games.ISobrietyGameServices)_game;
            EventRouter = (IEventRouter)_game.Services.GetService(typeof(IEventRouter));
        }

        public SobrietyEngine.Games.ISobrietyGameServices GameServices()
        {
            return gameServices;
        }

        public override string ToString()
        {
            String str = "ActorBase( ";
            str += "Name: " + name + ", ";
            str += Guid.ToString() + ", ";
            str += "UpdateActive: " + UpdateActor.ToString();
            str += ")";
            return str;

        }

        public void AttachComponent(SobrietyEngine.Components.IComponent component)
        {
            if (component == null)
            {
                Logger.GetSingleton().WriteError("Error- BaseActor::AttachComponent():: component is null!");
                return;
            }

            component.SetParent(this);

            if (!CompByType.ContainsKey(component.GetType()))
            {
                List<SobrietyEngine.Components.IComponent> l = new List<SobrietyEngine.Components.IComponent>();
                l.Add(component);
                CompByType.Add(component.GetType(), l);
            }
            else
            {
                if (!component.SingleInstance)
                    CompByType[component.GetType()].Add(component);
                else
                {
                    Logger.GetSingleton().WriteError("(" + Guid.Value + ")::AttachComponent - tried to attach a component from a family for which this actor already has a component");
                    throw new Exception("Immediate Error");
                }
            }


            Type[] supportedIfaces = component.SupportedInterfaces();
            foreach (Type type in supportedIfaces)
            {
                if (!CompBySupportedIFaces.ContainsKey(type))
                {
                    List<SobrietyEngine.Components.IComponent> l = new List<SobrietyEngine.Components.IComponent>();
                    l.Add(component);
                    CompBySupportedIFaces.Add(type, l);
                }
                else
                {
                    if (!component.SingleInstance) //we can have more than one component for this family
                        CompBySupportedIFaces[type].Add(component);
                    else
                    {
                        Logger.GetSingleton().WriteError("(" + Guid.Value + ")::AttachComponent - tried to attach a component from a family for which this actor already has a component");
                        throw new Exception("Immediate Error");
                    }
                }
            }

            //if we already initilized this actor, then init the coponent
            //unless someone has already done so
            if (Initialized)
            {
                if (component.Initialized == false)
                    component.InitComponent();
            }

            //add it to our lsit of updateable components
            if (component.RequiresUpdate)
                requiresUpdate.AddLast(component);
        }

        public Dictionary<Type, List<SobrietyEngine.Components.IComponent>> GetComponents()
        {
            return CompByType;
        }

        public void RemoveComponent(SobrietyEngine.Components.IComponent comp)
        {
            Type[] supportedIFaces = comp.SupportedInterfaces();
            foreach(Type type in supportedIFaces)
                if(CompBySupportedIFaces.ContainsKey(type))
                    CompBySupportedIFaces[type].Remove(comp);

            if(CompByType.ContainsKey(comp.GetType()))
                CompByType[comp.GetType()].Remove(comp);
            comp.Remove();
        }

        public T Get<T>()
        {
            return (T)Query(typeof(T));
        }

        public SobrietyEngine.Components.IComponent Query(Type type)
        {
            if (CompByType.ContainsKey(type))
            {
                if (CompByType[type].Count == 1)
                    return CompByType[type][0];
                else
                {
                    throw new Exception("Queried for one, found many componenets. not sure how to handle this now");
                }
            }
            if (CompBySupportedIFaces.ContainsKey(type))
            {
                if (CompBySupportedIFaces[type].Count == 1)
                    return CompBySupportedIFaces[type][0];
                else
                {
                    Logger.GetSingleton().WriteError("Queried for one, found many componenets");
                }
            }
            return null;
        }

        public List<SobrietyEngine.Components.IComponent> QueryList(Type type)
        {
            if (CompByType.ContainsKey(type))
            {
                return CompByType[type];
            }
            return null;
        }

        public void Initialize()
        {
            if (Initialized)
            {
                Logger.GetSingleton().WriteError("Actor " + name + " already initialized");
                return;
            }
            Initialized = true;

            //init our components
            Queue<SobrietyEngine.Components.IComponent> delayInit = new Queue<Components.IComponent>();
            foreach (List<SobrietyEngine.Components.IComponent> l in CompByType.Values)
            {
                foreach (SobrietyEngine.Components.IComponent i in l)
                {
                    //once we add the id component, set worldUp our GUid and name
                    if (i.GetType() == typeof(Components.Common.IdCmp))
                    {
                        IdCmp idcomp = (IdCmp)i;
                        guid.NameGuid(idcomp.Id);
                        name = idcomp.Id;
                    }
                    if (i.InitComponent() == false)
                        delayInit.Enqueue(i);
                }
            }

            //init again, for things that faled to init the first timeInMs
            while (delayInit.Count > 0)
            {
                SobrietyEngine.Components.IComponent c = delayInit.Dequeue();
                if (c.InitComponent() == false)
                {
                    Logger.GetSingleton().WriteError(c, "Component delayed initilization too many times");
                }
            }


            //register our components with the scene
            Scene.RegisterComponents(this);

            //raise our init event
            EventRouter.RaiseEvent(this, new Events.Actor.ActorInitialized(Guid));
        }

        public void Update(GameTime ElapsedTime)
        {
            if (!UpdateActor)
                return;

            foreach (IComponent ic in requiresUpdate)
                if (ic.Active)
                    ic.Update(ElapsedTime);
        }

        public void UnregisterEventListeners()
        {
            foreach (List<SobrietyEngine.Components.IComponent> l in CompByType.Values)
            {
                foreach (SobrietyEngine.Components.IComponent i in l)
                {
                    IEventListener iel = i as IEventListener;
                    if(iel == null)
                        continue;
                    iel.UnregisterEventListeners();
                }
            }
        }

        public void RegisterEventListeners()
        {
            foreach (List<SobrietyEngine.Components.IComponent> l in CompByType.Values)
            {
                foreach (SobrietyEngine.Components.IComponent i in l)
                {
                    IEventListener iel = i as IEventListener;
                    if (iel == null)
                        continue;
                    iel.RegisterEventListeners();
                }
            }
        }

        public void Remove()
        {
            foreach (List<SobrietyEngine.Components.IComponent> l in CompByType.Values)
            {
                foreach (SobrietyEngine.Components.IComponent i in l)
                {
                    i.Remove();
                }
            }
            CompBySupportedIFaces.Clear();
            CompByType.Clear();
            Guid.Remove();
        }
    }
}